//
// Created by Jinyu Zhu on 2022/4/18.
//

#ifndef PROVDB_TAG_GRAPH_OUTER_H
#define PROVDB_TAG_GRAPH_OUTER_H
#include "string"
#include "map"
#include "vector"
#include <stdexcept>

class CheckSum{
private:
    static const int MxMask = 0x0FFFFFFF;
    int Val;
public:
    CheckSum(): Val(0){}
    CheckSum(const CheckSum& Cs): Val(Cs.Val & MxMask){}
    explicit CheckSum(const int& Int): Val(Int & MxMask){}

    CheckSum& operator=(const CheckSum& Cs){ Val=Cs.Val; return *this;}
    bool operator==(const CheckSum& Cs) const {return Val == Cs.Val;}
    CheckSum& operator+=(const CheckSum& Cs){ Val= (Val + Cs.Val) & MxMask; return *this;}
    CheckSum& operator+=(const char& Ch){ Val= (Val + Ch) & MxMask; return *this;}
    CheckSum& operator+=(const int& Int){ Val= (Val + Int) & MxMask; return *this;}
    int Get() const {return Val;}

    static CheckSum GetCsFromBf(char* Bf, const int& BfL) {
        CheckSum Cs;
        for (int BfC=0; BfC<BfL; BfC++){Cs+=Bf[BfC];}
        return Cs;
    }
};

class FIn {
private:
    static const int MaxBfSize = 16*1024;
    char* Buffer; int BfL, BfC;
    FILE* FileId;
    CheckSum Cs;

private:
    void FillBf();

public:
    explicit FIn(const std::string& InFilePath);
    ~FIn();

    int GetBf(const void* LBf, const int& LBfL);

private:
    void Load(char*& CStr);

public:
    void Load(int& Int) { Cs += GetBf(&Int, sizeof(Int)); }
    void Load(bool& Bool) { Cs += GetBf(&Bool, sizeof(Bool)); }
    void Load(char& Char) { Cs += GetBf(&Char, sizeof(Char)); }
    void Load(double& Flt) { Cs += GetBf(&Flt, sizeof(Flt)); }
    void Load(long& Long) { Cs += GetBf(&Long, sizeof(Long)); }
    std::string LoadStr();

    void Load(std::vector<int>& VInt);
    void Load(std::vector<std::string>& VStr);
    void Load(std::map<std::string, int>& SIMap);
    void Load(std::map<std::string, std::vector<int> >& SVMap);
};

class FOut {
private:
    static const int MaxBfSize = 16*1024;
    char* Buffer; int BufferLen;
    FILE* FileId;
    CheckSum Cs;

private:
    void FlushBf();

public:
    explicit FOut(const std::string& OutFilePath);
    ~FOut();

    int PutCh(const char& Ch);
    int PutBf(const void* SrcBf, const int& SrcBfL);

public:
    void Save(const int& Int) { Cs += PutBf(&Int, sizeof(Int)); }
    void Save(const long& Long) { Cs += PutBf(&Long, sizeof(Long)); }
    void Save(const bool& Bool) { Cs += PutBf(&Bool, sizeof(Bool)); }
    void Save(const char& Char) { Cs += PutBf(&Char, sizeof(Char)); }
    void Save(const double& Flt) { Cs += PutBf(&Flt, sizeof(Flt)); }
    void Save(const std::string& Str) {
        if (Str.length() > 127) throw std::runtime_error("String " + Str + " is too long (>127)");
        Cs += PutCh(char(Str.size()));
        if (!Str.empty()) Cs += PutBf(Str.c_str(), (int) Str.size());
    }

    void Save(const std::vector<int>& VInt);
    void Save(const std::vector<std::string>& VStr);
    void Save(const std::map<std::string, int>& SIMap);
    void Save(const std::map<std::string, std::vector<int> >& SVMap);

};

template<class U>
void save_map_basic(U& map, FOut &fout) {
    int size = (int) map.size();
    fout.Save(size);
    for (const auto & iter : map) {
        fout.Save(iter.first);
        iter.second.Save(fout);
    }
}

template<class K, class V>
void load_map_basic(std::map<K, V>& map, FIn &fin) {
    if (!map.empty()) throw std::runtime_error("load to initialized map only!");
    int MSize; fin.Load(MSize);
    for (int i = 0; i < MSize; i++) {
        K key; fin.Load(key);
        V val(fin);
        map.insert({ key, val });
    }
}

#endif //PROVDB_TAG_GRAPH_OUTER_H
